#include "ChrzFile.h"
#include "SwitchEndianness.h"
#include <fstream>
#include <string.h>

using namespace std;


ChrzNode*                   GetChrzNode(char *chrz,UINT fcount);
bool                        GetAniFmt(UINT fmtcode,ChrzAniFmtInfo **ret);
ChrzKeyFrameCollection*     GetKeyframeColletion(char *chrz,UINT fcount,ChrzAniFmtInfo *afi);
bool                        FreeChrzNode(ChrzNode *cnode);
bool                        FreeKeyFrameCollection(ChrzKeyFrameCollection* kfs);

ChrzKeyFrameData*           GetI4KeyFrameData(char  *chrz,UINT &fcount);
ChrzKeyFrameData*           GetI6KeyFrameData(char  *chrz,UINT &fcount);
ChrzKeyFrameData*           GetI12KeyFrameData(char *chrz,UINT &fcount);
ChrzKeyFrameData*           GetL1KeyFrameData(char *chrz,UINT &fcount);
ChrzKeyFrameData*           GetL4KeyFrameData(char *chrz,UINT &fcount);


bool        readchrz(char *chrz,ChrzFile  **r)
{
    if(!chrz)
        return false;

    UINT            chrzver,roffset,sfsize,rfcount;
    char            *cur;
    BrresNode       *root;
    ChrzNode        *cnode;
    ChrzHeader      *chrzhd;
    BrresSubFileHeader  *sfheader = (BrresSubFileHeader*)chrz;
    //chrzver = u32se(sfheader->sfver);
    sfsize = u32se(sfheader->sfsize);
    cur = chrz+sizeof(BrresSubFileHeader);
    roffset = u32se(*((UINT*)cur));

    cur += (u32se(sfheader->sfver)==0x5 ? 0xc:8) ;
    chrzhd = (ChrzHeader*)cur;
    rfcount = u16se(chrzhd->framecount)+1;
    cur = chrz+roffset;

    root = GetDirNode(cur,NULL,roffset);

    if(!root)
        return false;
    *r = new ChrzFile;

    BrresNode *animes = root;

    for(int i=0; i<animes->childcount ; i++)
    {
        cnode = GetChrzNode(chrz+animes->children[i]->offset,rfcount);
        if(cnode)
            (*r)->aninodes.push_back(cnode);
    }
    (*r)->framecount = u16se(chrzhd->framecount)+1;
    (*r)->nodecount = (*r)->aninodes.size();

    FreeBrresNode(root);

    return true;

}

bool    readchrzfromfile(const char *fname,ChrzFile  **r)
{
    UINT    fsize;
    char    *chrzbuf;
    ifstream chrzfs;
    bool    result;

    //open bres file
    chrzfs.open(fname,ios::in | ios::binary);

    //if failed to open brres,return
    if(!chrzfs.is_open())
        return false;

    //get file size
    chrzfs.seekg(0,ios::end);
    fsize = chrzfs.tellg();
    chrzfs.seekg(0,ios::beg);

    //allocate memory and read file
    chrzbuf = new char[fsize];
    chrzfs.read(chrzbuf,fsize);
    chrzfs.close();

    result = readchrz(chrzbuf,r);

    delete[] chrzbuf;
    return result;
}

void        freechrz(ChrzFile  *r)
{
    if(!r)
        return;

    for(int i=0;i<r->aninodes.size();i++)
        FreeChrzNode(r->aninodes[i]);

    r->aninodes.clear();

    delete r;

    return;
}

////////////////////////////////////


ChrzNode*   GetChrzNode(char *chrz,UINT fcount)
{
    if(!chrz)
        return  NULL;

    UINT                        bnameoffset,bnamelen;
    ChrzNode                    *r;
    ChrzAniFmtInfo              *anifmtinf[3];
    ChrzKeyFrameCollection      *kfcols[3]={NULL};
    ChrzBoneAniInfo   *bainf = (ChrzBoneAniInfo*)chrz;
    bnameoffset = u32se(bainf->anameoffset);

    if(!GetAniFmt(bainf->fmtcode,anifmtinf))
        return  NULL;

    r = new ChrzNode;

    bnamelen = *((UINT*)(chrz+bnameoffset-4));
    bnamelen = u32se(bnamelen);

    r->bname = new char[bnamelen+1];

    memcpy(r->bname,chrz+bnameoffset,bnamelen+1);

    for(int i=0; i<3 ; i++)
    {
        if(!anifmtinf[i])
            continue;

        kfcols[i] = GetKeyframeColletion(chrz,fcount, anifmtinf[i]);

    }

    r->scalekeys    = kfcols[0];
    r->rotkeys      = kfcols[1];
    r->transkeys    = kfcols[2];

    return r;
}



bool     GetAniFmt(UINT fmtcode,ChrzAniFmtInfo **ret)
{
    if(!ret)
        return false;
//the order should be scal,rotate,translate
    UINT    fmtmask[3]={0x6000000,0x38000000,0xc0000000},
            fmtshift[3]={0x19,0x1b,0x1e},
            haskeymask=0x400000,
            xfmask=0x2000,yfmask=0x4000 ,zfmask=0x8000,
            imask=0x10,doffset=0x8;

    fmtcode = u32se(fmtcode);

    for(int i=0; i<3 ; i++,haskeymask<<=1,xfmask<<=3,yfmask<<=3,zfmask<<=3,imask<<=1)
    {
        if(!(haskeymask & fmtcode))
        {
            ret[i] = NULL;
            continue;
        }

        ret[i]= new ChrzAniFmtInfo;
        ret[i]->format = (CHRZKEYFRAMEFORMAT)((fmtmask[i] & fmtcode)>>fmtshift[i]);
        ret[i]->dataoffset = doffset;
        ret[i]->isotropic = ((imask & fmtcode) !=0 ? true : false);
        ret[i]->xfixed = ((xfmask & fmtcode) != 0 ? true : false);
        ret[i]->yfixed = ((yfmask & fmtcode) != 0 ? true : false);
        ret[i]->zfixed = ((zfmask & fmtcode) != 0 ? true : false);

        if(!ret[i]->isotropic)
            doffset+=0xc;
        else
            doffset+=4;

    }



    return true;
}


ChrzKeyFrameCollection*     GetKeyframeColletion(char *chrz,UINT fcount,ChrzAniFmtInfo *afi)
{
    ChrzKeyFrameCollection      *r;
    if(!chrz || !afi)
        return NULL;
    char    *xdp,*ydp,*zdp;
    bool    *isfixed,islinear;
    UINT     roffset;
    ChrzKeyFrameData*  (*keydatafunc)(char*,UINT&);

    switch(afi->format)
    {
        case I4:
            keydatafunc = GetI4KeyFrameData;
            islinear = false;
            break;
        case I6:
            keydatafunc = GetI6KeyFrameData;
            islinear = false;
            break;
        case I12:
            keydatafunc = GetI12KeyFrameData;
            islinear = false;
            break;
        case L1:
            keydatafunc = GetL1KeyFrameData;
            islinear = true;
            break;
        case L4:
            keydatafunc = GetL4KeyFrameData;
            islinear = true;
            break;
        default:
            return NULL;
    }


    if(afi->isotropic)
        xdp=ydp=zdp=chrz+afi->dataoffset;
    else
    {
        xdp = chrz+afi->dataoffset;
        ydp = xdp + 4;
        zdp = ydp + 4;
    }

    r = new ChrzKeyFrameCollection;
    r->islinear = islinear;

    if(afi->xfixed)     //if value fixed
    {
        r->xkeycount = 1;
        r->xkeys = new ChrzKeyFrameData;
        r->xkeys->frameid = 1;
        r->xkeys->tan = 0;
        r->xkeys->value = floatse(*((float*)xdp));
    }
    else
    {
        roffset = *((UINT*)xdp);
        roffset = u32se(roffset);
        xdp = chrz + roffset;
        r->xkeycount = fcount;
        r->xkeys = keydatafunc(xdp,r->xkeycount);
    }

    if(afi->yfixed)
    {
        r->ykeycount = 1;
        r->ykeys = new ChrzKeyFrameData;
        r->ykeys->frameid = 1;
        r->ykeys->tan = 0;
        r->ykeys->value = floatse(*((float*)ydp));
    }
    else
    {
        roffset = *((UINT*)ydp);
        roffset = u32se(roffset);
        ydp = chrz + roffset;
        r->ykeycount = fcount;
        r->ykeys = keydatafunc(ydp,r->ykeycount);
    }

    if(afi->zfixed)
    {
        r->zkeycount = 1;
        r->zkeys = new ChrzKeyFrameData;
        r->zkeys->frameid = 1;
        r->zkeys->tan = 0;
        r->zkeys->value = floatse(*((float*)zdp));
    }
    else
    {
        roffset = *((UINT*)zdp);
        roffset = u32se(roffset);
        zdp = chrz + roffset;
        r->zkeycount = fcount;
        r->zkeys = keydatafunc(zdp,r->zkeycount);
    }

    return r;
}


ChrzKeyFrameData*           GetI4KeyFrameData(char  *chrz,UINT &fcount)
{
    if(!chrz || fcount==0)
        return NULL;

    char *cur= chrz;
    Interpolated4_6  *kinfo = (Interpolated4_6*)cur;
    I4FrameData    *fd;
    float       vbase,vstep;
    UINT         tmp;

    fcount = u16se(kinfo->keyframecount);
    vbase  = floatse(kinfo->base);
    vstep  = floatse(kinfo->step);
    cur += sizeof(Interpolated4_6);

    ChrzKeyFrameData    *keys = new ChrzKeyFrameData[fcount];
    fd = (I4FrameData*)cur ;

    for(int i=0;i<fcount;i++,fd++)
    {
       keys[i].frameid = fd->frameid + 1;
       tmp        = fd->sandtan[1] & 0xf ;
       tmp <<= 8;
       tmp += fd->sandtan[2];
       keys[i].tan     = *((float*)&tmp) / 32.0f;
       tmp             = fd->sandtan[0];
       tmp  <<= 4;
       tmp  +=  ((fd->sandtan[1] & 0xf0)>>4);
        keys[i].value   = vbase + vstep * (float)tmp;
    }

    return  keys;

}


ChrzKeyFrameData*           GetI6KeyFrameData(char  *chrz,UINT &fcount)
{
    if(!chrz || fcount==0)
        return NULL;

    char    *cur= chrz;
    float   vbase,vstep;
    Interpolated4_6  *kinfo = (Interpolated4_6*)cur;
    I6FrameData    *fd;

    fcount = u16se(kinfo->keyframecount);
    vbase = floatse(kinfo->base);
    vstep = floatse(kinfo->step);


    cur += sizeof(Interpolated4_6);

    ChrzKeyFrameData    *keys = new ChrzKeyFrameData[fcount];
    fd = (I6FrameData*)cur ;

    for(int i=0;i<fcount;i++,fd++)
    {
       keys[i].frameid = (u16se(fd->frameid) >> 5) +1;
       USHORT tmp      = u16se(fd->tan);
       keys[i].tan     = (float)tmp / 256.0f;
       tmp             = u16se(fd->step);
       keys[i].value   = vbase + vstep * (float)tmp ;
       //keys[i].value   = keys[i].tan /256.0f;
    }

    return  keys;
}

ChrzKeyFrameData*           GetI12KeyFrameData(char *chrz,UINT &fcount)
{
    if(!chrz || fcount==0)
        return NULL;

    char *cur= chrz;
    Interpolated12  *kinfo = (Interpolated12*)cur;
    I12FrameData    *fd;

    fcount = u16se(kinfo->keyframecount);
    cur += sizeof(Interpolated12);

    ChrzKeyFrameData    *keys = new ChrzKeyFrameData[fcount];
    fd = (I12FrameData*)cur ;

    for(int i=0;i<fcount;i++,fd++)
    {
       keys[i].frameid = (UINT)(floatse(fd->frameid)) +1 ;
       keys[i].tan        = floatse(fd->tan) ;
       keys[i].value   = floatse(fd->val);
    }
    return  keys;
}


ChrzKeyFrameData*           GetL1KeyFrameData(char *chrz,UINT &fcount)
{
    if(!chrz || fcount==0)
        return NULL;

    Linear1      *kinfo = (Linear1*)chrz;
    UCHAR        *cur = (UCHAR*)(chrz+sizeof(Linear1));
    float       vbase,vstep;

    ChrzKeyFrameData    *keys = new ChrzKeyFrameData[fcount];
    vbase   = floatse(kinfo->base);
    vstep   = floatse(kinfo->step);

    for(int i=0;i<fcount-1;i++,cur++)
    {
        keys[i].frameid = i+1;
        keys[i].value = vbase + vstep * (*cur);
        //keys[i].tan = floatse(*(cur+1)) - keys[i].value;
    }

    keys[fcount-1].frameid = fcount;
    keys[fcount-1].value = vbase+ vstep * (*cur);
    keys[fcount-1].tan = 0;

    return keys;

}


ChrzKeyFrameData*           GetL4KeyFrameData(char *chrz,UINT &fcount)
{
    if(!chrz || fcount==0)
        return NULL;

    float        *cur = (float*)chrz;
    ChrzKeyFrameData    *keys = new ChrzKeyFrameData[fcount];

    for(int i=0;i<fcount-1;i++,cur++)
    {
        keys[i].frameid = i+1;
        keys[i].value = floatse(*cur);
        keys[i].tan = floatse(*(cur+1)) - keys[i].value;
    }

    keys[fcount-1].frameid = fcount;
    keys[fcount-1].value = floatse(*cur);
    keys[fcount-1].tan = 0;

    return keys;
}


bool    FreeChrzNode(ChrzNode *cnode)
{
    if(!cnode)
        return true;

    if(cnode->scalekeys)
        FreeKeyFrameCollection(cnode->scalekeys);

    if(cnode->rotkeys)
        FreeKeyFrameCollection(cnode->rotkeys);

    if(cnode->transkeys)
        FreeKeyFrameCollection(cnode->transkeys);

    delete[] cnode->bname;
    delete cnode;
    return true;
}


bool    FreeKeyFrameCollection(ChrzKeyFrameCollection* kfs)
{
    if(!kfs)
        return true;

    if(kfs->xkeycount!=0)
        delete[] kfs->xkeys;

    if(kfs->ykeycount!=0)
        delete[] kfs->ykeys;

    if(kfs->zkeycount!=0)
        delete[] kfs->zkeys;

    return true;
}
